package com.yerbamateprimer.round.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yerbamateprimer.model.Group;
import com.yerbamateprimer.model.Mate;
import com.yerbamateprimer.model.User;
import com.yerbamateprimer.round.Round;
import com.yerbamateprimer.round.state.State;
import com.yerbamateprimer.round.state.StateName;

/**
 * This class models a regular round
 * of yerba mate, without penalties and
 * the primer is participating in the
 * drink round.
 * 
 * @author Matias Liwski
 * @version 1.0
 */
public class RegularRound implements Round {
	private static transient Logger LOGGER = LoggerFactory.getLogger(RegularRound.class);
	private static final long serialVersionUID = 4723216871110060462L;
	private Mate mate;
	private State currentState;
	private User primer;
	private List<User> invitedUsers;
	private User currentDrinker;
	private List<User> drinkers;
	private Map<StateName, State> stateMap;

	/**
	 * Default constructor.
	 */
	public RegularRound() {
		this.resetRound();
	}

	/**
	 * @param states The possible states that this Round could have. 
	 */
	public RegularRound(List<State> states) {
		this.stateMap = new HashMap<StateName, State>();
		for (State state : states) {
			addState(state);
		}
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#setMate(com.yerbamateprimer.model.Mate)
	 */
	@Override
	public void setMate(Mate mate) {
		this.mate = mate;
		this.stateMap.get(StateName.BUILDING).setAutoGetOutDelay(mate.getBuildingTime());
		this.stateMap.get(StateName.DRINKING).setAutoGetOutDelay(mate.getDrinkingTime());
		this.stateMap.get(StateName.PRIMING).setAutoGetOutDelay(mate.getPrimingTime());
		this.stateMap.get(StateName.RESTING).setAutoGetOutDelay(mate.getRestingTime());
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#getMate()
	 */
	@Override
	public Mate getMate() {
		return this.mate;
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#inviteUser(com.yerbamateprimer.model.User)
	 */
	@Override
	public void inviteUser(User user) {
		this.invitedUsers.add(user);
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#inviteGroup(com.yerbamateprimer.model.Group)
	 */
	@Override
	public void inviteGroup(Group group) {
		List<User> groupMembers = group.getMembers();
		groupMembers.remove(this.primer);
		groupMembers.removeAll(this.drinkers);
		groupMembers.removeAll(this.invitedUsers);
		
		this.invitedUsers.addAll(groupMembers);
	}

	/**
	 * Return a safe copy of users invited to this round.
	 * 
	 * @see com.yerbamateprimer.round.Round#getInvitedUsers()
	 */
	@Override
	public List<User> getInvitedUsers() {
		return new ArrayList<User>(this.invitedUsers);
	}

	/**
	 * Moves the provided user from the invited list to the drinkers list.
	 * 
	 * @param user The user to confirm.
	 * @see com.yerbamateprimer.round.Round#confirmUser(com.yerbamateprimer.model.User)
	 */
	@Override
	public void confirmUser(User user) {
		this.invitedUsers.remove(user);
		this.drinkers.add(user);
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#removeUser(com.yerbamateprimer.model.User)
	 */
	@Override
	public void removeUser(User user) {
		this.drinkers.remove(user);
		this.invitedUsers.remove(user);
	}

	/**
	 * This round doesn't have penance.
	 * 
	 * @see com.yerbamateprimer.round.Round#putPenanceToUser(com.yerbamateprimer.model.User)
	 */
	@Override
	public void putUserInPenance(User user) {
		LOGGER.warn("The method addPenanceDrinker was invoked (User={}).", user);
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#setPrimer(com.yerbamateprimer.model.User)
	 */
	@Override
	public void setPrimer(User user) {
		this.primer = user;
		this.drinkers.add(user);
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#getCurrentPrimer()
	 */
	@Override
	public User getPrimer() {
		return this.primer;
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#setCurrentDrinker(com.yerbamateprimer.model.User)
	 */
	@Override
	public void setCurrentDrinker(User user) {
		this.currentDrinker = user;
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#getCurrentDrinker()
	 */
	@Override
	public User getCurrentDrinker() {
		return this.currentDrinker;
	}

	@Override
	public User rotateCurrentDrinker() {
		Collections.rotate(this.drinkers, 1);
		User nextDrinker = this.drinkers.get(0);
		setCurrentDrinker(nextDrinker);
		
		return nextDrinker;
	}

	/**
	 * Return a safe copy of users participating in this round (The drinkers).
	 * 
	 * @see com.yerbamateprimer.round.Round#getDrinkers()
	 */
	@Override
	public List<User> getDrinkers() {
		return new ArrayList<User>(this.drinkers);
	}

	/**
	 * This round doesn't have penance.
	 * 
	 * @see com.yerbamateprimer.round.Round#getPenanceParticipants()
	 */
	@Override
	public List<User> getPenanceParticipants() {
		LOGGER.warn("The method getPenanceDrinkers was invoked");
		return Collections.emptyList();
	}

	/**
	 * This round doesn't have penance.
	 * 
	 * @see com.yerbamateprimer.round.Round#releaseUserFromPenance(com.yerbamateprimer.model.User)
	 */
	@Override
	public void releaseUserFromPenance(User user) {
		LOGGER.warn("The method removePenanceDrinker was invoked (User={}).", user);
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#getCurrentState()
	 */
	@Override
	public State getCurrentState() {
		return this.currentState;
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#setCurrentState(com.yerbamateprimer.round.state.State)
	 */
	@Override
	public void setCurrentState(State state) {
		this.currentState = state;
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#addState(com.yerbamateprimer.round.state.State)
	 */
	@Override
	public void addState(State state) {
		this.stateMap.put(state.getName(), state);
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#getState(com.yerbamateprimer.round.state.StateName)
	 */
	@Override
	public State getState(StateName stateName) {
		return this.stateMap.get(stateName);
	}

	/**
	 * A safe copy of the possible states for this round.
	 * 
	 * @see com.yerbamateprimer.round.Round#getStates()
	 */
	@Override
	public List<State> getStates() {
		return new ArrayList<State>(this.stateMap.values());
	}

	/* (non-Javadoc)
	 * @see com.yerbamateprimer.round.Round#resetRound()
	 */
	@Override
	public void resetRound() {
		this.mate = null;
		this.currentState = null;
		this.primer = null;
		this.currentDrinker = null;
		this.invitedUsers = new ArrayList<User>();
		this.drinkers = new ArrayList<User>();
		this.stateMap = new HashMap<StateName, State>();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE, false, true);
	}
}